using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.Text;
using Microsoft.SqlServer.Server;
using System.Xml;
using System.Reflection;
using System.Net;
using System.IO;
using System.Diagnostics;
using System.Threading;
using System.Runtime.CompilerServices;

namespace Microsoft.MSE.Repository.Triggers
{
	public class CLRTriggers {
        static Dictionary<int, int> sEventDamn = new Dictionary<int, int>();
        
		static string EventSrc = "MSE6 DB";

		private static object staticLock = new object();

        public static Dictionary<int, int> EventDamn
        {
            get { return sEventDamn; }
        }

		static CLRTriggers() {
			Trace.Listeners.Add(new EventLogTraceListener(EventSrc));
		}

        public static void BindingsTrigger()
        {
            SqlTriggerContext ctx = SqlContext.TriggerContext;

            //if the Binding is new or being deleted, no runtime servers can be impacted so don't do anything.
            if (ctx.TriggerAction == TriggerAction.Insert || ctx.TriggerAction == TriggerAction.Delete)
                return;

            SqlCommand cmdOPV;
            SqlCommand cmdEP;
            SqlCommand cmdRS;

            List<Guid> idList = new List<Guid>();
            List<DBEventDataItem> eventDataOPV = new List<DBEventDataItem>();
            List<DBEventDataItem> eventDataEP = new List<DBEventDataItem>();
            List<DBEventDataItem> eventDataRS = new List<DBEventDataItem>();

            using (SqlConnection conn = new SqlConnection(@"context connection=true"))
            {
                conn.Open();
                //get list of op versions that were impacted (only join to INSERTED since delete's are ignored (see above)
                StringBuilder sb = new StringBuilder();
                sb.Append("SELECT OV.OperationVersionId FROM INSERTED INNER JOIN ServiceEngine.Channels C ON INSERTED.BindingID = C.BindingID ");
                sb.Append("INNER JOIN ServiceEngine.OperationVersions OV ON C.ChannelID = OV.ChannelID ");
                cmdOPV = new SqlCommand(sb.ToString(), conn);
                using (SqlDataReader reader1 = cmdOPV.ExecuteReader())
                {
                    while (reader1.Read())
                    {
                        idList.Add(reader1.GetGuid(0));
                    }
                }
                //lookup runtime server for each op version affected
                if (idList.Count > 0)
                    eventDataOPV = GetRuntimeServersForOperationVersionId(idList, conn);

                //get list of endpoints impacted (Only lookup against INSERTED since delete's are ignored (see above))
                idList.Clear();
                cmdEP = new SqlCommand(@"SELECT e.EndpointID FROM [ServiceEngine].Endpoints e WHERE e.BindingID IN (SELECT BindingID FROM INSERTED);", conn);
                using (SqlDataReader reader1 = cmdEP.ExecuteReader())
                {
                    if (reader1.HasRows)
                    {
                        while (reader1.Read())
                        {
                            idList.Add(reader1.GetGuid(0));
                        }
                    }
                }
                //lookup runtime server for each endpoint affected
                if (idList.Count > 0)
                    eventDataEP = GetRuntimeServersForEndpointId(idList, conn);

                //get list of runtime servers impacted (Only lookup against INSERTED since delete's are ignored (see above))
                idList.Clear();
                StringBuilder sbRS = new StringBuilder();
                sbRS.Append(@"SELECT rs.RuntimeServerID FROM [ServiceEngine].RuntimeServers rs ");
                sbRS.Append(" WHERE rs.CatalogBindingID IN (SELECT BindingID FROM INSERTED)");
                sbRS.Append(" OR rs.NotificationBindingID IN (SELECT BindingID FROM INSERTED)");
                sbRS.Append(" OR rs.ListenerBindingID IN (SELECT BindingID FROM INSERTED)");
                cmdRS = new SqlCommand(sbRS.ToString(), conn);
                using (SqlDataReader reader1 = cmdRS.ExecuteReader())
                {
                    if (reader1.HasRows)
                    {
                        while (reader1.Read())
                        {
                            DBEventDataItem dbeventData = new DBEventDataItem(reader1.GetGuid(0));
                            eventDataRS.Add(dbeventData);
                        }
                    }
                }
            }

            //consolidate eventdata
            //if EndpointsUpdated event is going to be thrown for an Endpoint, there is no need to throw OperationsUpdated events for the same endpoint
            if (eventDataEP != null && eventDataEP.Count > 0 && eventDataOPV != null && eventDataOPV.Count > 0)
            {
                foreach (DBEventDataItem epItem in eventDataEP)
                {
                    for (int i = eventDataOPV.Count-1; i >= 0; i--)
                    {
                        if (epItem.RuntimeServerId == eventDataOPV[i].RuntimeServerId && epItem.EndpointId == eventDataOPV[i].EndpointId)
                            eventDataOPV.RemoveAt(i);
                    }
                }
            }

            //if no runtime servers impacted, don't throw the event
            if (eventDataOPV != null && eventDataOPV.Count > 0) RaiseEvent(DBEvents.OperationsUpdated, ctx.TriggerAction, eventDataOPV);
            if (eventDataEP != null && eventDataEP.Count > 0) RaiseEvent(DBEvents.EndpointsUpdated, ctx.TriggerAction, eventDataEP);
            if (eventDataRS != null && eventDataRS.Count > 0) RaiseEvent(DBEvents.RuntimeServersUpdated, ctx.TriggerAction, eventDataRS);
        }

        public static void ChannelsTrigger()
        {
            SqlTriggerContext ctx = SqlContext.TriggerContext;

            //if the channel is new or being deleted, no runtime servers can be impacted so don't do anything.
            if (ctx.TriggerAction == TriggerAction.Insert || ctx.TriggerAction == TriggerAction.Delete)
                return;

            List<DBEventDataItem> eventData = new List<DBEventDataItem>();
            using (SqlConnection conn = new SqlConnection(@"context connection=true"))
            {
                conn.Open();
                //get list of op versions that were touched (Only lookup against INSERTED since delete's are ignored (see above))
                List<Guid> idList = new List<Guid>();
                SqlCommand cmd = new SqlCommand("SELECT OV.OperationVersionId FROM INSERTED INNER JOIN ServiceEngine.OperationVersions OV ON INSERTED.ChannelID = OV.ChannelID ", conn);
                using (SqlDataReader reader1 = cmd.ExecuteReader())
                {
                    while (reader1.Read())
                    {
                        idList.Add(reader1.GetGuid(0));
                    }
                }

                //lookup runtime server for each op version affected
                eventData = GetRuntimeServersForOperationVersionId(idList, conn);
            }

            //if no runtime servers impacted, don't throw the event
            if (eventData != null && eventData.Count > 0)
                RaiseEvent(DBEvents.OperationsUpdated, ctx.TriggerAction, eventData);

        }

        public static void EndpointsTrigger()
        {
            SqlTriggerContext ctx = SqlContext.TriggerContext;

            //if the endpoint is new or being deleted, no runtime servers can be impacted so don't do anything.
            if (ctx.TriggerAction == TriggerAction.Insert || ctx.TriggerAction == TriggerAction.Delete)
                return;

            List<DBEventDataItem> eventData = new List<DBEventDataItem>();
            using (SqlConnection conn = new SqlConnection(@"context connection=true"))
            {
                conn.Open();
                //get list of endpoints that were touched
                List<Guid> idList = GetIdListFromTriggerContextTables("EndpointID", conn);

                //lookup runtime server for each endpoint affected
                eventData = GetRuntimeServersForEndpointId(idList, conn);
            }

            //raise event only if a runtime server was impacted
            if (eventData != null && eventData.Count > 0)
                RaiseEvent(DBEvents.EndpointsUpdated, ctx.TriggerAction, eventData);
        }

        public static void RuntimeServersTrigger()
        {
            SqlTriggerContext ctx = SqlContext.TriggerContext;

            //no need to throw an event when adding a runtime server, no endpoints would be assigned to it yet.
            if (ctx.TriggerAction == TriggerAction.Insert)
                return;

            List<DBEventDataItem> eventData = new List<DBEventDataItem>();
            using (SqlConnection conn = new SqlConnection(@"context connection=true"))
            {
                conn.Open();
                SqlCommand cmd = null;
                //get list of runtime servers that were touched
                if (ctx.TriggerAction == TriggerAction.Delete)
                {
                    cmd = new SqlCommand("SELECT RuntimeServerID FROM DELETED", conn);
                }
                else
                {
                    cmd = new SqlCommand("SELECT RuntimeServerID FROM INSERTED", conn);
                }
                using (SqlDataReader reader1 = cmd.ExecuteReader())
                {
                    while (reader1.Read())
                    {
                        DBEventDataItem dbeventData = new DBEventDataItem(reader1.GetGuid(0));
                        eventData.Add(dbeventData);
                    }
                }
            }
            RaiseEvent(DBEvents.RuntimeServersUpdated, ctx.TriggerAction, eventData);
        }

        public static void RuntimeServersEndpointsTrigger()
        {
            SqlTriggerContext ctx = SqlContext.TriggerContext;
            List<DBEventDataItem> eventData = new List<DBEventDataItem>();
            using (SqlConnection conn = new SqlConnection(@"context connection=true"))
            {
                conn.Open();
                SqlCommand cmd = null;
                //get list of runtime servers that were touched
                if (ctx.TriggerAction == TriggerAction.Delete)
                {
                    cmd = new SqlCommand("SELECT RS.RuntimeServerID, EndpointID FROM DELETED INNER JOIN ServiceEngine.RuntimeServers RS ON DELETED.RuntimeServerID = RS.RuntimeServerID", conn);
                }
                else
                {
                    cmd = new SqlCommand("SELECT RS.RuntimeServerID, EndpointID FROM INSERTED INNER JOIN ServiceEngine.RuntimeServers RS ON INSERTED.RuntimeServerID = RS.RuntimeServerID", conn);
                }
                using (SqlDataReader reader1 = cmd.ExecuteReader())
                {
                    while (reader1.Read())
                    {
                        DBEventDataItem dbeventData = new DBEventDataItem(reader1.GetGuid(0), reader1.GetGuid(1));
                        eventData.Add(dbeventData);
                    }
                }
            }
            RaiseEvent(DBEvents.EndpointsUpdated, ctx.TriggerAction, eventData);
        }

        public static void MessagePartsTrigger()
        {
            return;
            //This trigger is not needed.  MessageParts are only ever modified as part of updating an operation version.  
            //Let the operation version trigger handle eventing

            //SqlTriggerContext ctx = SqlContext.TriggerContext;
            //List<DBEventDataItem> eventData = new List<DBEventDataItem>();
            //using (SqlConnection conn = new SqlConnection(@"context connection=true"))
            //{
            //    conn.Open();
            //    //get list of op versions that were touched
            //    List<Guid> idList = GetIdListFromTriggerContextTables("OperationVersionID", conn);

            //    //lookup runtime server for each op version affected
            //    eventData = GetRuntimeServersForOperationVersionId(idList, conn);
            //}
            //RaiseEvent(DBEvents.OperationsUpdated, ctx.TriggerAction, eventData);
        }

        public static void OperationsTrigger()
        {
            SqlTriggerContext ctx = SqlContext.TriggerContext;

            //if the operation is new or being deleted, no runtime servers can be impacted so don't do anything.
            //its the operation's association with an endpoint that impacts runtime servers
            if (ctx.TriggerAction == TriggerAction.Insert || ctx.TriggerAction == TriggerAction.Delete)
                return;

            List<DBEventDataItem> eventData = new List<DBEventDataItem>();
            using (SqlConnection conn = new SqlConnection(@"context connection=true"))
            {
                conn.Open();
                //get list of ops that were touched
                List<Guid> idList = GetIdListFromTriggerContextTables("OperationID", conn);

                //lookup runtime server for each operation affected
                eventData = GetRuntimeServersForOperationId(idList, conn);
            }

            //only throw the event if operations are associated with a runtime server
            if (eventData != null && eventData.Count > 0)
                RaiseEvent(DBEvents.OperationsUpdated, ctx.TriggerAction, eventData);
        }

        public static void OperationVersionsTrigger()
        {
            SqlTriggerContext ctx = SqlContext.TriggerContext;

            //adding/deleting a new operation version doesn't impact runtimes.  
            //the event will be triggered by the OV-endpoint association.
            if (ctx.TriggerAction == TriggerAction.Insert || ctx.TriggerAction == TriggerAction.Delete)
                return;

            List<DBEventDataItem> eventData = new List<DBEventDataItem>();
            using (SqlConnection conn = new SqlConnection(@"context connection=true"))
            {
                conn.Open();
                //get list of op versions that were touched
                List<Guid> idList = GetIdListFromTriggerContextTables("OperationVersionID", conn);

                //lookup runtime server for each op version affected
                eventData = GetRuntimeServersForOperationVersionId(idList, conn);
            }

            //only raise the event if there is a runtime server affected
            if (eventData != null && eventData.Count > 0)
                RaiseEvent(DBEvents.OperationsUpdated, ctx.TriggerAction, eventData);
        }

        public static void PoliciesTrigger()
        {
            SqlTriggerContext ctx = SqlContext.TriggerContext;

            //if the policy is new or being deleted, no runtime servers can be impacted so don't do anything.  
            if (ctx.TriggerAction == TriggerAction.Insert || ctx.TriggerAction == TriggerAction.Delete)
                return;

            SqlCommand cmdEP;
            SqlCommand cmdOP;
            SqlCommand cmdOPV;

            List<Guid> idList = new List<Guid>();
            List<DBEventDataItem> eventDataEP = new List<DBEventDataItem>();
            List<DBEventDataItem> eventDataOP = new List<DBEventDataItem>();
            List<DBEventDataItem> eventDataOPV = new List<DBEventDataItem>();

            using (SqlConnection conn = new SqlConnection(@"context connection=true"))
            {
                conn.Open();
                cmdEP = new SqlCommand(@"SELECT e.EndpointID FROM [ServiceEngine].Endpoints e WHERE e.PolicyID IN (SELECT PolicyID FROM INSERTED UNION SELECT PolicyID FROM DELETED);", conn);
                using (SqlDataReader reader1 = cmdEP.ExecuteReader())
                {
                    if (reader1.HasRows)
                    {
                        while (reader1.Read())
                        {
                            idList.Add(reader1.GetGuid(0));
                        }
                    }
                }
                //lookup runtime server for each endpoint affected
                if (idList.Count > 0)
                    eventDataEP = GetRuntimeServersForEndpointId(idList, conn);

                //get operations impacted due to policy change linked directly to an operation
                idList.Clear();
                cmdOP = new SqlCommand(@"SELECT o.OperationID FROM [ServiceEngine].Operations o WHERE o.PolicyID IN (SELECT PolicyID FROM INSERTED UNION SELECT PolicyID FROM DELETED);", conn);
                using (SqlDataReader reader2 = cmdOP.ExecuteReader())
                {
                    if (reader2.HasRows)
                    {
                        while (reader2.Read())
                        {
                            idList.Add(reader2.GetGuid(0));
                        }
                    }
                }
                //lookup runtime server for each operation affected
                if (idList.Count > 0)
                    eventDataOP = GetRuntimeServersForOperationId(idList, conn);

                //Get operation versions impacted due to policy change linked to channel
                idList.Clear();
                cmdOPV = new SqlCommand(@"SELECT DISTINCT ov.OperationVersionID FROM [ServiceEngine].Entity_Policies epol " +
                                    " INNER JOIN [ServiceEngine].Channels c ON epol.EntityID = c.ChannelID " +
                                    " INNER JOIN [ServiceEngine].OperationVersions ov ON c.ChannelID = ov.ChannelID" +
                                    " WHERE epol.PolicyID IN (SELECT PolicyID FROM INSERTED UNION SELECT PolicyID FROM DELETED);", conn);
                using (SqlDataReader reader3 = cmdOPV.ExecuteReader())
                {
                    if (reader3.HasRows)
                    {
                        while (reader3.Read())
                        {
                            idList.Add(reader3.GetGuid(0));
                        }
                    }
                }
                //lookup runtime server for each operationversion affected
                if (idList.Count > 0)
                    eventDataOPV = GetRuntimeServersForOperationVersionId(idList, conn);

                //consolidate impacted runtime servers between operations and operationversions changes 
                //(since both would fire the OperationsUpdated event)
                if (eventDataOP == null || eventDataOP.Count==0)
                    eventDataOP = eventDataOPV;
                else if (eventDataOPV != null && eventDataOPV.Count > 0)
                {
                    for (int i = 0; i < eventDataOPV.Count; i++)
                    {
                        DBEventDataItem temp = eventDataOPV[i];
                        if (eventDataOP.Contains(temp))
                        {
                            eventDataOP.Add(temp);
                        }
                    }
                }
            }
            if (eventDataEP != null && eventDataEP.Count > 0) RaiseEvent(DBEvents.EndpointsUpdated, ctx.TriggerAction, eventDataEP);
            if (eventDataOP != null && eventDataOP.Count > 0) RaiseEvent(DBEvents.OperationsUpdated, ctx.TriggerAction, eventDataOP);
        }

        public static void SchemasTrigger()
        {
            SqlTriggerContext ctx = SqlContext.TriggerContext;

            //if the schema is new or being deleted, no runtime servers can be impacted so don't do anything.
            if (ctx.TriggerAction == TriggerAction.Insert || ctx.TriggerAction == TriggerAction.Delete)
                return;

            List<DBEventDataItem> eventData = new List<DBEventDataItem>();
            using (SqlConnection conn = new SqlConnection(@"context connection=true"))
            {
                conn.Open();
                //get list of op versions that were touched
                List<Guid> idList = new List<Guid>();
                StringBuilder sb = new StringBuilder();
                sb.Append("SELECT DISTINCT OV.OperationId FROM INSERTED INNER JOIN ServiceEngine.MessageParts MP ON INSERTED.SchemaID = MP.SchemaID ");
                sb.Append(" INNER JOIN ServiceEngine.OperationVersions OV ON OV.OperationVersionID = MP.OperationVersionID");
                SqlCommand cmd = new SqlCommand(sb.ToString(), conn);
                using (SqlDataReader reader1 = cmd.ExecuteReader())
                {
                    while (reader1.Read())
                    {
                        idList.Add(reader1.GetGuid(0));
                    }
                }
                if (idList.Count > 0)
                {
                    //lookup runtime server for each op affected
                    eventData = GetRuntimeServersForOperationId(idList, conn);
                }
            }

            //only throw the event if operations are associated with a runtime server
            if (eventData != null && eventData.Count > 0)
                RaiseEvent(DBEvents.OperationsUpdated, ctx.TriggerAction, eventData);
        }

        public static void OperationGroupsTrigger()
        {
            RaiseEvent(DBEvents.OperationsUpdated);
        }

        public static void Endpoints_AssociatedOperationsTrigger()
        {
            SqlTriggerContext ctx = SqlContext.TriggerContext;

            //for operations becoming active at an endpoint, no event needs to be published.
            if (ctx.TriggerAction == TriggerAction.Insert)
                return;

            List<DBEventDataItem> eventData = new List<DBEventDataItem>();
            using (SqlConnection conn = new SqlConnection(@"context connection=true"))
            {
                conn.Open();

                SqlCommand cmd = null;

                //get list of operationid's for the affected endpoint & operationversions
                StringBuilder sb = new StringBuilder(8);
                sb.Append("SELECT DISTINCT re.RuntimeServerID, re.EndpointID, ov.OperationID");
                sb.Append(" FROM ServiceEngine.RuntimeServers_Endpoints re ");
                if (ctx.TriggerAction == TriggerAction.Delete)
                    sb.Append(" INNER JOIN DELETED t ON t.EndpointID = re.EndpointID ");
                else
                    sb.Append(" INNER JOIN INSERTED t ON t.EndpointID = re.EndpointID ");
                sb.Append(" INNER JOIN ServiceEngine.OperationVersions ov on ov.OperationVersionID = t.OperationVersionID ");


                cmd = new SqlCommand(sb.ToString(), conn);
                using (SqlDataReader reader1 = cmd.ExecuteReader())
                {
                    while (reader1.Read())
                    {
                        DBEventDataItem dbeventData = new DBEventDataItem(reader1.GetGuid(0),  reader1.GetGuid(1), reader1.GetGuid(2));
                        eventData.Add(dbeventData);
                    }
                }
            }
            //raise event only if affected endpoint is associated with a runtime server
            if (eventData != null && eventData.Count > 0)
                RaiseEvent(DBEvents.EndpointOpAssociationUpdated, ctx.TriggerAction, eventData);
        }

        public static void Endpoints_PublishedOperationsTrigger()
        {
            SqlTriggerContext ctx = SqlContext.TriggerContext;

            List<DBEventDataItem> eventData = new List<DBEventDataItem>();
            using (SqlConnection conn = new SqlConnection(@"context connection=true"))
            {
                conn.Open();

                SqlCommand cmd = null;

                //get list of operationid's for the affected endpoint & operationversions
                StringBuilder sb = new StringBuilder(8);
                sb.Append("SELECT DISTINCT re.RuntimeServerID, re.EndpointID, ov.OperationID");
                sb.Append(" FROM ServiceEngine.RuntimeServers_Endpoints re ");
                if (ctx.TriggerAction == TriggerAction.Delete)
                    sb.Append(" INNER JOIN DELETED t ON t.EndpointID = re.EndpointID ");
                else
                    sb.Append(" INNER JOIN INSERTED t ON t.EndpointID = re.EndpointID ");
                sb.Append(" INNER JOIN ServiceEngine.OperationVersions ov on ov.OperationVersionID = t.OperationVersionID ");

                cmd = new SqlCommand(sb.ToString(), conn);
                using (SqlDataReader reader1 = cmd.ExecuteReader())
                {
                    while (reader1.Read())
                    {
                        DBEventDataItem dbeventData = new DBEventDataItem(reader1.GetGuid(0), reader1.GetGuid(1), reader1.GetGuid(2));
                        eventData.Add(dbeventData);
                    }
                }
            }
            //raise event only if affected endpoint is associated with a runtime server
            if (eventData != null && eventData.Count > 0)
                RaiseEvent(DBEvents.EndpointOpPublicationUpdated, ctx.TriggerAction, eventData);
        }

        #region Trigger Support
        private static DBChangeType GetChangeType(TriggerAction triggerAction)
        {
            switch (triggerAction)
            {
                case TriggerAction.Insert:
                    return DBChangeType.Create;
                case TriggerAction.Update:
                    return DBChangeType.Update;
                case TriggerAction.Delete:
                    return DBChangeType.Delete;
                default:
                    return DBChangeType.Unknown;
            }
        }
        private static List<Guid> GetIdListFromTriggerContextTables(string idColumnName, SqlConnection conn)
        {
            List<Guid> idList = new List<Guid>();
            SqlCommand cmd = new SqlCommand(string.Format("SELECT {0} FROM INSERTED UNION SELECT {1} FROM DELETED;", idColumnName, idColumnName), conn);
            using (SqlDataReader reader1 = cmd.ExecuteReader())
            {
                while (reader1.Read())
                {
                    idList.Add(reader1.GetGuid(0));
                }
            }
            return idList;
        }
        private static List<DBEventDataItem> GetRuntimeServersForEndpointId(List<Guid> idList, SqlConnection conn)
        {
            List<DBEventDataItem> eventData = new List<DBEventDataItem>();
            foreach (Guid id in idList)
            {
                SqlCommand cmd2;
                cmd2 = new SqlCommand("[ServiceEngine].ListRuntimeServersForEndpointID", conn);
                cmd2.CommandType = CommandType.StoredProcedure;
                SqlParameter param = new SqlParameter("@EndpointID", SqlDbType.UniqueIdentifier);
                param.Value = id;
                cmd2.Parameters.Add(param);
                using (SqlDataReader reader2 = cmd2.ExecuteReader())
                {
                    while (reader2.Read())
                    {
                        DBEventDataItem dbeventData = new DBEventDataItem(reader2.GetGuid(0), reader2.GetGuid(1));
                        eventData.Add(dbeventData);
                    }
                }
            }
            return eventData;
        }
        private static List<DBEventDataItem> GetRuntimeServersForOperationId(List<Guid> idList, SqlConnection conn)
        {
            List<DBEventDataItem> eventData = new List<DBEventDataItem>();
            foreach (Guid id in idList)
            {
                SqlCommand cmd2;
                cmd2 = new SqlCommand("[ServiceEngine].ListRuntimeServersForOperationID", conn);
                cmd2.CommandType = CommandType.StoredProcedure;
                SqlParameter param = new SqlParameter("@OperationID", SqlDbType.UniqueIdentifier);
                param.Value = id;
                cmd2.Parameters.Add(param);
                using (SqlDataReader reader2 = cmd2.ExecuteReader())
                {
                    while (reader2.Read())
                    {
                        DBEventDataItem dbeventData = new DBEventDataItem(reader2.GetGuid(0), reader2.GetGuid(1), reader2.GetGuid(2));
                        eventData.Add(dbeventData);
                    }
                }
            }
            return eventData;
        }
        private static List<DBEventDataItem> GetRuntimeServersForOperationVersionId(List<Guid> idList, SqlConnection conn)
        {
            List<DBEventDataItem> eventData = new List<DBEventDataItem>();
            foreach (Guid id in idList)
            {
                SqlCommand cmd2;
                cmd2 = new SqlCommand("[ServiceEngine].ListRuntimeServersForOperationVersionID", conn);
                cmd2.CommandType = CommandType.StoredProcedure;
                SqlParameter param = new SqlParameter("@OperationVersionID", SqlDbType.UniqueIdentifier);
                param.Value = id;
                cmd2.Parameters.Add(param);
                using (SqlDataReader reader2 = cmd2.ExecuteReader())
                {
                    while (reader2.Read())
                    {
                        DBEventDataItem dbeventData = new DBEventDataItem(reader2.GetGuid(0), reader2.GetGuid(1), reader2.GetGuid(2));
                        eventData.Add(dbeventData);
                    }
                }
            }
            return eventData;
        }
        #endregion

        protected static void RaiseEvent(DBEvents dbEvent)
        {
            RaiseEvent(dbEvent, new List<DBEventDataItem>());
        }
        protected static void RaiseEvent(DBEvents dbEvent, List<DBEventDataItem> eventData)
        {
            RaiseEvent(dbEvent, TriggerAction.Invalid ,eventData);
        }
        protected static void RaiseEvent(DBEvents dbEvent, TriggerAction triggerAction, List<DBEventDataItem> eventData)
        {
            try
            {
                int eventDamnSignal = 0;
                RaiseEventInfo raiseEventInfo = new RaiseEventInfo(dbEvent, eventData);
                raiseEventInfo.EventContext.ChangeType = GetChangeType(triggerAction);

                lock (sEventDamn)
                {
                    int hashKey = raiseEventInfo.EventContext.GetHashCode();
                    if (!sEventDamn.ContainsKey(hashKey))
                        sEventDamn.Add(hashKey, 0);
                    eventDamnSignal = ++sEventDamn[hashKey];
                }
                if (eventDamnSignal == 1)
                {
                    SqlCommand cmd;
                    SqlParameter pEventName;

                    using (SqlConnection conn = new SqlConnection(@"context connection=true"))
                    {
                        conn.Open();
                        cmd = new SqlCommand("[ServiceEngine].ListCallbackURIsByEventName", conn);
                        pEventName = new SqlParameter("@EventName", SqlDbType.VarChar, 100);
                        pEventName.Value = Enum.GetName(typeof(DBEvents), dbEvent);
                        cmd.Parameters.Add(pEventName);
                        cmd.CommandType = CommandType.StoredProcedure;
                        using (SqlDataReader reader = cmd.ExecuteReader())
                        {
                            if (reader.HasRows)
                            {
                                while (reader.Read())
                                {
                                    raiseEventInfo.AddTarget(reader.GetString(0));
                                }
                            }
                        }
                    }

                    WaitCallback wc = new WaitCallback(ProcessRaiseEventRequest);
                    ThreadPool.QueueUserWorkItem(wc, raiseEventInfo);
                }
            }
            catch (Exception ex)
            {
                Trace.TraceError("MSE6DB RaiseEvent failed with [" + ex.Message + "]");
            }

        }
        private static void WaitOutEventStorm(DBEventContext eventContext)
        {
            bool eventStormPossible = true;
            int hashKey = eventContext.GetHashCode();

            while (eventStormPossible)
            {  // Ride out Event Storms
                Thread.Sleep(200);  // Wait for signs of possible event storm
                lock (sEventDamn)
                {
                    if (!sEventDamn.ContainsKey(hashKey))
                        sEventDamn.Add(hashKey, 0);

                    if (sEventDamn[hashKey] <= 1)
                    {
                        eventStormPossible = false; // No further events in 200ms so no storm
                        // Since hash key is based on the event context data, we need to remove items from the dictionary to keep size under control
                        sEventDamn.Remove(hashKey); 
                    }
                    else
                    {
                        sEventDamn[hashKey] = 1; // Reset single event signal to see if more events are incomming
                    }
                }
            }
        }
        protected static void ProcessRaiseEventRequest(object o)
        {
            try
            {
                RaiseEventInfo raiseEventInfo = o as RaiseEventInfo;

                WaitOutEventStorm(raiseEventInfo.EventContext);

                //todo:  update any UDDI registries listed in the catalog

                foreach (string URI in raiseEventInfo.EventTargets)
                {

                    try
                    {
                        HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(URI);
                        request.ContentType = @"text/xml";
                        request.Method = "POST";
                        request.Headers.Add("SOAPAction: \"http://services.microsoft.com/2006-07/ServicePlatform/MSE6/Eventing/AcceptEvent\"");
                        try
                        {
                            using (Stream stream = request.GetRequestStream())
                            {
                                using (StreamWriter writer = new StreamWriter(stream))
                                {
                                    writer.Write(@"<s:Envelope xmlns:s='http://schemas.xmlsoap.org/soap/envelope/'><s:Body xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:xsd='http://www.w3.org/2001/XMLSchema'><AcceptEventMsg xmlns='http://services.microsoft.com/2006-07/ServicePlatform/MSE6'><AcceptEvent xmlns='http://schemas.xmlsoap.org/ws/2004/08/eventing'>");
                                    writer.Write(@"<ChangeType xmlns='http://services.microsoft.com/2006-07/ServicePlatform/MSE6/Eventing'>");
                                    writer.Write(Enum.GetName(typeof(DBChangeType), raiseEventInfo.EventContext.ChangeType));
                                    writer.Write(@"</ChangeType><Event xmlns='http://services.microsoft.com/2006-07/ServicePlatform/MSE6/Eventing'>");
                                    writer.Write(Enum.GetName(typeof(DBEvents), raiseEventInfo.EventContext.Event));
                                    writer.Write(@"</Event><EventData xmlns='http://services.microsoft.com/2006-07/ServicePlatform/MSE6/Eventing'>");
                                    foreach (DBEventDataItem data in raiseEventInfo.EventContext.EventData)
                                    {
                                        writer.Write(string.Format("<DBEventDataItem><EndpointId>{0}</EndpointId><OperationId>{1}</OperationId><RuntimeServerId>{2}</RuntimeServerId></DBEventDataItem>", data.EndpointId, data.OperationId, data.RuntimeServerId));
                                    }
                                    writer.Write(@"</EventData></AcceptEvent></AcceptEventMsg></s:Body></s:Envelope>");
                                    writer.Flush();
                                }

                            }

                            HttpWebResponse response = (HttpWebResponse)request.GetResponse();
                        }
                        catch (WebException wex)
                        {
                            string exResponse = "";
                            if (wex.Response != null)
                            {
                                XmlDocument xmlResponse = new XmlDocument();
                                xmlResponse.Load(wex.Response.GetResponseStream());
                                exResponse = xmlResponse.OuterXml;
                            }
                            Trace.TraceError(string.Format("Callback returned an error to RaiseEvent trigger [{0}], extended Response was [{1}]", wex.Message, exResponse));
                        }
                        catch (ProtocolViolationException pex)
                        {
                            Trace.TraceError(string.Format("Protocol Violation Exception throw by RaiseEvent trigger [{0}]", pex.Message));
                        }
                        catch (Exception ex)
                        {
                            Trace.TraceError(string.Format("Exception throw by RaiseEvent when notifying " + URI + " [{0}]", ex.Message));
                        }
                    }
                    catch (Exception ex)
                    {
                        Trace.TraceError(string.Format("RaiseEvent trigger failed to create HttpWebRequest for " + URI + " [{0}]", ex.Message));
                    }
                }
            }
            catch (Exception ex)
            {
                Trace.TraceError("MSE6DB Failed to process RaiseEvent request [" + ex.Message + "]");
            }
        }
    }


    internal class RaiseEventInfo
    {
        private List<string> eventTargets;
        private DBEventContext eventContext;

        public RaiseEventInfo(DBEvents dbEvent)
            :this(dbEvent, new List<DBEventDataItem>())
        {
        }
        public RaiseEventInfo(DBEvents dbEvent, List<DBEventDataItem> dbEventData)
        {
            this.eventTargets = new List<string>();
            this.eventContext = new DBEventContext(dbEvent, dbEventData);
        }

        public void AddTarget(string URI)
        {
            this.eventTargets.Add(URI);
        }

        public List<string> EventTargets
        {
            get { return this.eventTargets; }
            set { this.eventTargets = value; }
        }

        public DBEventContext EventContext
        {
            get { return this.eventContext; }
            set { this.eventContext = value; }
        }
  }

}
